﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace SharpCrack
{
	/// <summary>
	/// 
	/// </summary>
	public class SequentialHashCracker : HashCracker
	{
		/// <summary>
		/// Initializes a new instance of SequentialHashCracker.
		/// </summary>
		public SequentialHashCracker()
		{
			this.Hashes = new List<byte[]>();
			this.HashFunctionExponent = 1;
			this.WordTransforms = new List<WordTransform>();
			this.WordTransforms.Add(new AsIsWordTransform());
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="str"></param>
		public override void TryString(string candidate)
		{
			byte[] hash = this.Algorithm.ComputeHash(
				Encoding.Default.GetBytes(candidate)
			);

			// Hash N times
			for (int i = 1; i < this.HashFunctionExponent; i++)
				hash = this.Algorithm.ComputeHash(hash);

			IList<byte[]> foundHashes = new List<byte[]>();

			// Match
			foreach (byte[] h in this.Hashes)
			{
				if (ByteArrayHelper.CompareByteArrays(hash, h))
				{
					foundHashes.Add(h);

					OnCollisionFound(new CollisionFoundEventArgs(h, candidate));
				}
			}

			foreach (byte[] fh in foundHashes)
				this.Hashes.Remove(fh);

			// Stop if all hashes are cracked
			if (this.Hashes.Count == 0)
				this.CancellationPending = true;

			this.allGuesses++;
		}

		/// <summary>
		/// 
		/// </summary>
		public override void StartBruteForceAttack(string charset, int minLength, int maxLength, string startingPermutation)
		{
			if (String.IsNullOrEmpty(charset))
				throw new ArgumentNullException("charset", "charset can not be null or an empty string.");

			if (maxLength < minLength)
				throw new ArgumentException("Maximum length must be greater than or equal minimum length.", "maxLength");

			if (startingPermutation != null && (startingPermutation.Length < minLength || startingPermutation.Length > maxLength))
				throw new ArgumentOutOfRangeException("startingPermutation", startingPermutation, "The length of starting permutation must be between the specified minimum and maximum length.");

			this.InitializeAttack();
			this.OnAttackStarted(new AttackStartedEventArgs() { Mode = AttackMode.BruteForce });

			for (int pl = (
					startingPermutation != null ? Math.Max(minLength, startingPermutation.Length) : minLength
				); pl <= maxLength; pl++)
			{
				PermutationGenerator gen = new PermutationGenerator(charset, pl);

				// Set starting position
				if (startingPermutation != null && pl == startingPermutation.Length)
					gen.SetStartingPermutation(startingPermutation);

				// Enumerate permutations
				foreach (string s in gen)
				{
					TryString(s);

					if (this.CancellationPending)
						return;
				}
			}

			this.OnAttackFinished(new AttackFinishedEventArgs() { Guessess = allGuesses });
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="wordlistPath"></param>
		public override void StartDictionaryAttack(string wordlistPath, int initialPosition = 0)
		{
			if (String.IsNullOrEmpty(wordlistPath))
				throw new ArgumentNullException("wordlistPath", "wordlistPath can not be null or an empty string.");

			if (!File.Exists(wordlistPath))
				throw new ArgumentException("Wordlist can not be found.", new FileNotFoundException("Wordlist can not be found.", wordlistPath));

			if (initialPosition < 0)
				throw new ArgumentOutOfRangeException("initialPosition", initialPosition, "initialPosition can not be negative.");

			this.WordlistPosition = 0;

			this.InitializeAttack();
			this.OnAttackStarted(new AttackStartedEventArgs() { Mode = AttackMode.Dictionary });

			using (StreamReader sr = new StreamReader(wordlistPath))
			{
				while (!sr.EndOfStream)
				{
					string line = sr.ReadLine();

					// Skip to start position
					if (this.WordlistPosition++ < initialPosition)
						continue;

					// Skip empty lines
					if (line.Length == 0)
						continue;

					// Stop if requested
					if (this.CancellationPending)
						break;

					// Process line
					this.ProcessCandidate(line);
				}
			}

			this.OnAttackFinished(new AttackFinishedEventArgs() { Guessess = allGuesses });
		}
	}
}
